package aa;

import java.nio.*;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.lang.*;
import java.net.InetSocketAddress;

class sockData {
	boolean doR, doW, peerFull, tellMyFull, peerClose;
	byte[] data, id;

	sockData(byte[] d) {
		doR = doW = peerClose = peerFull = tellMyFull = false;
		data = new byte[0];
		id = d;
	}
}
class logClass
{
	public void setLog(String st)
	{
		helpFunc.print(st);
	}
	}
public class connc {
	ServerSocketChannel accepSock;
	SocketChannel connSock;
	Map<String, SocketChannel> idMap = new HashMap<String, SocketChannel>();
	Map<SocketChannel, sockData> proxySocks = new HashMap<SocketChannel, sockData>();

	byte[] peerBuf, readCache;
	int peerBufMax=helpFunc.con_streamBufferSize;
	int connNum = 0;
	int maxNum = 900;
	byte[] decodeData;
	byte[] decodeId;
	int decodeType;
	int askConn_g = 1;
	int connBack_g = 2;
	int full_g = 3;
	int notfull_g = 4;
	int data_g = 5;
	int close_g = 6;
	int cachePerConn = 200 * 1024;
	logClass myLog; 
	public connc(String serverip, int localPort,int increaseDose,int deDose,int pushA,
			 int sizeLi,int limit,int speed,ArrayList<Integer> lp,byte[] salt,logClass lo)
					 throws Exception
	 {
		connSock = SocketChannel.open();
		myLog = lo;
		streamc st = new streamc(serverip, increaseDose, deDose, pushA, sizeLi, limit, speed, lp, salt, lo);
		
		myThr aa = new myThr();
		aa.st = st;
		aa.setDaemon(true);
		aa.start();
		InetSocketAddress add2 = new InetSocketAddress("localhost", st.listenPort);
		connSock.connect(add2);
		myLog.setLog("connc starts");			
		InetSocketAddress add = new InetSocketAddress("0.0.0.0", localPort);
		accepSock = ServerSocketChannel.open();
		accepSock.configureBlocking(false);
		accepSock.bind(add);
		peerBuf = new byte[0];
		readCache = new byte[0];
		connSock.configureBlocking(false);
		while (true) {
			try {
				doWork();
			} catch (Exception e) {
				helpFunc.print(e.toString());
			}
		}
	}

	void rig(Selector s) throws Exception {
		for (SocketChannel sock : proxySocks.keySet()) {
			sockData d = proxySocks.get(sock);
			d.doR = d.doW = false;
			int k=0;
			if (peerBuf.length < peerBufMax && (!d.peerFull)) {
				d.doR = true;
				k|=SelectionKey.OP_READ;			
			}
			if (d.data.length > 0) {
				d.doW = true;
				k|=SelectionKey.OP_WRITE;				
			}
			if(k!=0)
			{
				sock.register(s, k);
			}
		}
	}

	void dealAccep() throws Exception {

		SocketChannel sock = accepSock.accept();
		sock.configureBlocking(false);
		connNum += 1;
helpFunc.print("ask conn");
		if (connNum > maxNum) {
			sock.close();
			connNum -= 1;
			helpFunc.print("too much conn");
		}

		else {
			byte[] id = helpFunc.getuuid_byte();
			sock.configureBlocking(false);
			idMap.put(helpFunc.bytesToHex(id), sock);
			byte[] msg = makeMsg(askConn_g, id, new byte[1]);
			addPeerBuf(msg);
		}
	}

	void addPeerBuf(byte[] msg) {
		byte[] t = new byte[msg.length + peerBuf.length];
		System.arraycopy(peerBuf, 0, t, 0, peerBuf.length);
		System.arraycopy(msg, 0, t, peerBuf.length, msg.length);
		peerBuf = t;
	}

	byte[] makeMsg(int type, byte[] id, byte[] data) {
		structWrapper re = new structWrapper(5 + id.length + data.length);
		re.writeDWord(5 + id.length + data.length);
		re.writeByte((byte) type);
		re.writeArr(id);
		re.writeArr(data);
		return re.data;
	}

	void decodeMsg(byte[] msg) {
		structWrapper re = new structWrapper(msg);
		decodeType = re.readByte();
		decodeId = new byte[16];
		System.arraycopy(msg, 1, decodeId, 0, 16);
		decodeData = new byte[msg.length - 17];
		System.arraycopy(msg, 17, decodeData, 0, msg.length - 17);
	}

	void dealConnSockRead() throws Exception {
		ByteBuffer bu = ByteBuffer.allocate(65535);
		int bytesRead = connSock.read(bu);
		byte[] temp = new byte[readCache.length + bytesRead];
		System.arraycopy(readCache, 0, temp, 0, readCache.length);
		System.arraycopy(bu.array(), 0, temp, readCache.length, bytesRead);
		readCache = temp;
		ArrayList<byte[]> ll = dealData();

		for (byte[] msg : ll) {
			decodeMsg(msg);
			if (decodeType == connBack_g) {
				byte ok = decodeData[0];
				SocketChannel sock = idMap.get(helpFunc.bytesToHex(decodeId));
				if (ok == 1) {
					sockData d = new sockData(decodeId);
					proxySocks.put(sock, d);
				} else {
					connNum -= 1;
					sock.close();
					idMap.remove(helpFunc.bytesToHex(decodeId));
				}
			} else {
				dealNormalConnSock();
			}
		}
	}

	void dealNormalConnSock() {
		if(!idMap.containsKey(helpFunc.bytesToHex(decodeId))) return;
		SocketChannel sock = idMap.get(helpFunc.bytesToHex(decodeId));
		if (decodeType == full_g)
			proxySocks.get(sock).peerFull = true;
		if (decodeType == notfull_g)
			proxySocks.get(sock).peerFull = false;
		if (decodeType == data_g) {
			proxySocks.get(sock).data = helpFunc.bufAdd(proxySocks.get(sock).data, decodeData);
			if (proxySocks.get(sock).data.length > cachePerConn && (!proxySocks.get(sock).tellMyFull)) {
				byte[] msg = makeMsg(full_g, decodeId, new byte[1]);
				addPeerBuf(msg);
			}
		}
		if (decodeType == close_g) {
			proxySocks.get(sock).peerClose = true;
			if (proxySocks.get(sock).data.length == 0) {
				doClose(sock, decodeId);
			}
		}
	}

	void doClose(SocketChannel sock, byte[] id) {
		try {
			sock.close();
			connNum -= 1;
			proxySocks.remove(sock);
			idMap.remove(helpFunc.bytesToHex(id));
			helpFunc.print("close");
		} catch (Exception e) {
			helpFunc.print(e.toString());
		}
	}

	void dealProxySockR(SocketChannel sock) {
		byte[] temp;
		int bytesRead;
		ByteBuffer bu = ByteBuffer.allocate(65535);
		try {
			bytesRead = sock.read(bu);
			if(bytesRead<=0) throw new Exception();
			temp = new byte[bytesRead];
			System.arraycopy(bu.array(), 0, temp, 0, bytesRead);
		} catch (Exception e) {
			temp = new byte[0];
		}
		byte[] id = proxySocks.get(sock).id;
		if (temp.length == 0) {
			doClose(sock, id);
			addPeerBuf(makeMsg(close_g, id, new byte[1]));
			return;
		}
		addPeerBuf(makeMsg(data_g, id, temp));
	}

	void dealProxySockW(SocketChannel sock) {
		try {
			ByteBuffer temp = ByteBuffer.wrap(proxySocks.get(sock).data);
			sock.write(temp);
			proxySocks.get(sock).data = new byte[temp.remaining()];
			temp.get(proxySocks.get(sock).data, 0, proxySocks.get(sock).data.length);
			if (proxySocks.get(sock).data.length < cachePerConn && proxySocks.get(sock).tellMyFull) {
				byte[] id = proxySocks.get(sock).id;
				proxySocks.get(sock).tellMyFull = false;
				addPeerBuf(makeMsg(notfull_g, id, new byte[1]));
			}
			if (proxySocks.get(sock).data.length == 0 && proxySocks.get(sock).peerClose) {
				byte[] id = proxySocks.get(sock).id;
				doClose(sock, id);
			}
		} catch (Exception e) {
			byte[] id = proxySocks.get(sock).id;
			addPeerBuf(makeMsg(close_g, id, new byte[1]));
			doClose(sock, id);
		}
	}

	ArrayList<byte[]> dealData() {
		ArrayList<byte[]> msgList = new ArrayList<byte[]>();
		structWrapper re = new structWrapper(readCache);

		while (true) {
			if (re.leftLen() < 4)
				break;
			int ll = re.readDWord();

			if (re.leftLen() < ll-4)
			{	re.pos-=4;
				break;}
			msgList.add(re.readArr(ll-4));
		}
		readCache = re.getLeftData();
		return msgList;
	}

	public void doWork() throws Exception {
		Selector selector = Selector.open();
		boolean peerW = false;
		if (peerBuf.length > 0) {
			peerW = true;
			connSock.register(selector, SelectionKey.OP_WRITE| SelectionKey.OP_READ);
		}
		else {
			connSock.register(selector, SelectionKey.OP_READ);
		}
		accepSock.register(selector, SelectionKey.OP_ACCEPT);
		

		
		rig(selector);
		int ret = selector.select(1000);
		Iterator selectedKeys = selector.selectedKeys().iterator();
		while (selectedKeys.hasNext()) {
			SelectionKey key = (SelectionKey) selectedKeys.next();
			selectedKeys.remove();
			if (!key.isValid()) {
				continue;
			}
			SelectableChannel channel = key.channel();
			if (channel == accepSock && key.isAcceptable())
				dealAccep();
			if (channel == connSock && key.isReadable())
				dealConnSockRead();

			if (proxySocks.containsKey(channel) && key.isReadable() && proxySocks.get(channel).doR)
				dealProxySockR((SocketChannel) channel);

			if (proxySocks.containsKey(channel) && key.isWritable() && proxySocks.get(channel).doW)
				dealProxySockW((SocketChannel) channel);

			if (channel == connSock && peerW && key.isWritable()) {
				ByteBuffer temp = ByteBuffer.wrap(peerBuf);
				connSock.write(temp);
				peerBuf = new byte[temp.remaining()];
				temp.get(peerBuf, 0, peerBuf.length);
			}
		}
		selector.close();
	}

	public static void main(String[] args) throws Exception {
		/*
		SocketChannel so = SocketChannel.open();
		ArrayList<Integer> listenPort = new ArrayList<Integer>();
		for (int i = 50000; i < 50100; i++)
			listenPort.add(i);
		String ss = "salt";
		streamc st = new streamc(listenPort, ss.getBytes(), helpFunc.con_minRate, helpFunc.pushAhead,
				helpFunc.packLimit);
		myThr aa = new myThr();
		aa.st = st;
		aa.setDaemon(true);
		aa.start();
		InetSocketAddress add = new InetSocketAddress("localhost", st.listenPort);
		so.connect(add);
		System.out.println("ok");
	*/

	}

}
